/*
 * sys.c - Syscalls implementation
 */

#include <devices.h>
#include <utils.h>
#include <errno.h>
#include <sched.h>
#include <mm.h>
#include <io.h>
#include <stats.h>
#include <string.h>

int sys_sem_destroy(int n_sem);

int comprova_fd(int fd, int operacio)
{
	struct task_struct *cur = current();

	if((fd < 0) || (fd >= CHTABLE_SIZE))
		return -EBADF;
	if(cur->chtable[fd].used == 0)
		return -EBADF;
	else if(((cur->chtable[fd].opened_file->access_mode) & operacio) == 0)
		return -EBADF;
	return 0;
}

/* EXIT */
void sys_exit(void)
{
	int i, tsk;
	struct semaphore *sem;

	/* El proceso 0 no se puede borrar */
	if(current()->pid == 0) return;

	/* Busca donde esta ese pid en tasks */
	tsk = search_pid(current()->pid);
	if(tsk < 0) return;

	/* Liberar frames de datos del proceso */
	for(i = 0; i < NUM_PAG_DATA; ++i)
	{
		free_frame((unsigned int)(task[tsk].t.task.data_frames[i]));
	}

	/* LIBERAR SEMAFOROS */
	while(!list_empty(&(current()->sem_list)))
	{
		sem = list_head_to_sem(current()->sem_list.next);
		sys_sem_destroy(sem->n_sem);
	}

	/* Marcar como libre el task_struct */
	task[tsk].t.task.pid = -1;

	/* Scheduling */
	list_del(&(current()->run_list));
	scheduling();
}

/* FORK */
int sys_fork(void)
{
	int i, j, tsk;
	int allocated_frames[NUM_PAG_DATA];

	/* Allocate child's task in task array */
	tsk = alloc_task(); /* Mirar error -1 */
	if(tsk < 0) return -EAGAIN;

	/* Allocate NUM_PAG_DATA pages for the new process' data */
	for(i = 0; i < NUM_PAG_DATA; ++i)
	{
		allocated_frames[i] = alloc_frame();
		if(allocated_frames[i] == -1)
		{
			for(j = 0; j <= i; ++j)
				free_frame((unsigned int)(allocated_frames[j]));
			return -EAGAIN; /* Mirar error del errno */
		}
	}
	
	/* Map and copy parent's data to child's data frames */
	for (i = 0; i < NUM_PAG_DATA; ++i)
	{
		set_ss_pag(PAG_LOG_INIT_DATA_P0 + NUM_PAG_DATA + i, allocated_frames[i]);
		copy_data((void*)((PAG_LOG_INIT_DATA_P0 + i)<<12), (void*)((PAG_LOG_INIT_DATA_P0+NUM_PAG_DATA + i)<<12), PAGE_SIZE);
		del_ss_pag(PAG_LOG_INIT_DATA_P0 + NUM_PAG_DATA + i);
	}

	/* Flush TLB */
	set_cr3();

	/* Copy parent's task_struct to child's task_struct address */
	copy_data((void *)current(), (void *)&(task[tsk].t), PAGE_SIZE);

	/* Set child's info */
	pid++;
	task[tsk].t.task.pid = pid;
	task[tsk].t.task.tics = 0;
	task[tsk].t.task.total_tics = 0;
	task[tsk].t.task.total_trans = 0;
	INIT_LIST_HEAD(&(task[tsk].t.task.sem_list));

	/* Actualizar oft */
	for(i = 0; i < CHTABLE_SIZE; ++i)
	{
		if(task[tsk].t.task.chtable[i].used)
			task[tsk].t.task.chtable[i].opened_file->n_refs++;
	}

	for(i = 0; i < NUM_PAG_DATA; i++)
		task[tsk].t.task.data_frames[i] = allocated_frames[i];

	/* Insert new process in runqueue */
	list_add_tail(&(task[tsk].t.task.run_list), &runqueue);

	/* PID = 0 for child */
	task[tsk].t.stack[KERNEL_STACK_SIZE-10] = 0;

	return pid; /* pid */
}

/* READ */
int sys_read(int fd, char *buffer, int size)
{
	int read = 0;
	int fd_ok;
	struct task_struct *cur = current();

	if(size <= 0) return -1;
	fd_ok = comprova_fd(fd, READ);
	if(fd_ok < 0) return fd_ok;
	if(!(access_ok(READ, buffer, size))) return -EFAULT;

	read = cur->chtable[fd].opened_file->f->ops->sys_read_dev(buffer, size, &(cur->chtable[fd].opened_file->seek));

	return read;
}

/* WRITE */
int sys_write(int fd, const char *buffer, int size)
{
	char user_data[BUFFER_SIZE];
	int written = 0;
	int fd_ok = comprova_fd(fd, WRITE);
	struct task_struct *cur = current();

	if(fd_ok < 0) return fd_ok;
	if(buffer == NULL) return -EFAULT;
	if(size < 0) return -EINVAL;
	if(!(access_ok(WRITE, buffer, size))) return -EFAULT;

	while (size > BUFFER_SIZE)
	{
		copy_from_user((void*)(buffer+written), user_data, BUFFER_SIZE);
		written += cur->chtable[fd].opened_file->f->ops->sys_write_dev(buffer, size, &(cur->chtable[fd].opened_file->seek));
		size -= BUFFER_SIZE;
	}
	copy_from_user((void*)(buffer+written), user_data, size);
	written += cur->chtable[fd].opened_file->f->ops->sys_write_dev(buffer, size, &(cur->chtable[fd].opened_file->seek));

	return written;
}

/* OPEN */
int sys_open(const char *path, int flags)
{
	int file, fd, oft_entry;
	struct task_struct *cur = current();

	int pth = (int)path;
	pth = pth >> 12;

	if(path == NULL) return -EFAULT;

	if(flags >> 4) return -EINVAL;

	/* Path en espacio de kernel */
	if(pth < PAG_LOG_INIT_DATA_P0) return -EFAULT;

	if(!access_ok(READ, path, strlen((char*)path))) return -EFAULT;

	/* Path size */
	if(strlen((char*)path) > FILE_NAME_SIZE)
		return -ENAMETOOLONG;

	/* Buscar canal libre */
	fd = 0;
	while((fd < CHTABLE_SIZE) && (cur->chtable[fd].used)) ++fd;
	if(fd == CHTABLE_SIZE) return -EMFILE;

	/* Buscar entrada en OFT disponible */
	oft_entry = 0;
	while((oft_entry < OFT_SIZE) && (oft[oft_entry].n_refs > 0)) ++oft_entry;
	if(oft_entry == OFT_SIZE) return -ENFILE;

	/* Buscar archivo en directory */
	file = 0;
	while((file < MAX_FILES) && (strcmp(directory[file].name, (char*) path) || directory[file].free)) ++file;

	/* file no existe */
	if(file == MAX_FILES)
	{
		if(!(flags & O_CREAT))
			return -ENOENT;

		/* crearlo */
		else
		{
			if(flags & O_EXCL) return -EEXIST;

			if(free_block == -1) return -ENOSPC;

			/* Buscar entrada de directory libre para este file */
			file = 0;
			while((file < MAX_FILES) && (!directory[file].free)) ++file;
			if(file == MAX_FILES) return -1; /* PREG */

			/* Inicializar file */
			directory[file].fat_index = free_block;
			free_block = FAT[free_block];
			FAT[directory[file].fat_index] = EOF;
			if(directory[file].fat_index < 0) return -ENOSPC;
			copy_data((char*) path, directory[file].name, FILE_NAME_SIZE);
			directory[file].free = 0;
			directory[file].access_mode = O_RDWR; /* PREG */
			operations[file].sys_write_dev = (void*)sys_write_file;
			operations[file].sys_read_dev = (void*)sys_read_file;
			directory[file].ops = &operations[file];
			directory[file].ops->sys_open_dev = NULL;
			directory[file].ops->sys_close_dev = NULL;
			directory[file].ops->sys_release_dev = sys_release_file;
			directory[file].size = 0;
			directory[file].n_refs = 0;
		}
	}
	else
	{
		if((flags & O_CREAT) && (flags & O_EXCL)) return -EEXIST;
	}

	if((directory[file].access_mode != O_RDWR) && ((flags&0x03) != directory[file].access_mode))
		return -EACCES;

	oft[oft_entry].n_refs = 1;
	oft[oft_entry].seek = (int)&disk[directory[file].fat_index][0];
	oft[oft_entry].access_mode = flags & 0x03;
	oft[oft_entry].f = &directory[file];

	directory[file].n_refs++;

	cur->chtable[fd].used = 1;
	cur->chtable[fd].opened_file = &oft[oft_entry];

	return fd;
}

/* CLOSE */
int sys_close(int fd)
{
	struct task_struct *cur = current();
	if((fd < 0) || (fd >= CHTABLE_SIZE)) return -EBADF;
	if(!(cur->chtable[fd].used)) return -EBADF;

	cur->chtable[fd].used = 0;
	cur->chtable[fd].opened_file->n_refs--;
	if(cur->chtable[fd].opened_file->n_refs == 0)
		cur->chtable[fd].opened_file->f->n_refs--;

	return 0;
}

/* UNLINK */
int sys_unlink(const char *path)
{
	int file = 0;
	int pth = (int) path;
	pth = pth >> 12;

	if(path == NULL) return -EFAULT;
	if(pth < PAG_LOG_INIT_DATA_P0) return -EFAULT;
	if(strlen((char*)path) > FILE_NAME_SIZE) return -ENAMETOOLONG;

	while((file < MAX_FILES) && (strcmp(directory[file].name, (char*) path))) ++file;
	if(file == MAX_FILES) return -ENOENT;

	if(directory[file].n_refs > 0) return -EBUSY;

	directory[file].free = 1;
	free_FAT(directory[file].fat_index);

	return 0;
}

/* GETPID */
int sys_getpid(void)
{
	struct task_struct* t = current();
	return t->pid;
}

int sem_valid(int n_sem)
{
	if((n_sem < 0) || (n_sem >= NR_SEM)) /* ¿n_sem invalido? */
		return 0;
	if(sems[n_sem].n_sem < 0) /* ¿n_sem inicializado? */
		return 0;
	return 1;
}

/* SEM_INIT */
int sys_sem_init(int n_sem, unsigned int value)
{
	int sem;
	sem = alloc_sem(n_sem);
	if(sem < 0) return sem; /* ERROR */
	sems[n_sem].n_sem = n_sem;
	sems[n_sem].owner = current()->pid;
	sems[n_sem].sleepers = 0;
	sems[n_sem].value = value;
	INIT_LIST_HEAD(&sems[n_sem].waitqueue);
	list_add_tail(&(sems[n_sem].sem_list), &(current()->sem_list));
	return 0;
}

/* SEM_WAIT */
int sys_sem_wait(int n_sem)
{
	union task_union * t;
	if(!sem_valid(n_sem)) /* ¿n_sem valido? */
		return -EINVAL;
	if(current()->pid == 0)	/* ¿llamada hecha por task0? */
		return -EINVAL;
	if(sems[n_sem].value <= 0)
	{
		list_del(&(current()->run_list));
		list_add_tail(&(current()->run_list), &sems[n_sem].waitqueue);
		++sems[n_sem].sleepers;
		t = (union task_union*) current();
		t->stack[KERNEL_STACK_SIZE - 10] = 0;
		scheduling();
	}
	else
		--sems[n_sem].value;
	return 0;
}

/* SEM_SIGNAL */
int sys_sem_signal(int n_sem)
{
	struct list_head *l;
	if(!sem_valid(n_sem))	/* ¿n_sem valido? */
		return -EINVAL;
	if(sems[n_sem].sleepers == 0)
		++sems[n_sem].value;
	else
	{
		l = sems[n_sem].waitqueue.next;
		list_del(l);
		list_add_tail(l, &runqueue);
		--sems[n_sem].sleepers;
	}
	return 0;
}

/* SEM_DESTROY */
int sys_sem_destroy(int n_sem)
{
	struct list_head * l;
	union task_union * t;
	if(!sem_valid(n_sem))
		return -EINVAL;
	if(sems[n_sem].owner != current()->pid)
		return -EPERM;
	while(sems[n_sem].sleepers > 0)
	{
		l = sems[n_sem].waitqueue.next;
		list_del(l);
		list_add_tail(l, &runqueue);
		t = (union task_union*) list_head_to_task_struct(l);
		t->stack[KERNEL_STACK_SIZE-10] = -EINTR;
		--sems[n_sem].sleepers;
	}
	list_del(&(sems[n_sem].sem_list));
	sems[n_sem].n_sem = -1; /* Liberar espacio del semaforo */
	return 0;
}

/* NICE */
int sys_nice(int quantum)
{
	int old_nice = current()->quantum;
	if(quantum <= 0) return -EINVAL; /* ERROR */
	current()->quantum = quantum;
	return old_nice;
}

/* GET_STATS */
int sys_get_stats(int pid, struct stats *st)
{
	int pos = search_pid(pid);
	struct stats s;
	if(pid < 0)
		return -EINVAL;
	if(pos < 0)
		return -ESRCH;
	if(!access_ok(WRITE, st, 3*sizeof(int)))
		return -EFAULT;
	s.total_tics = task[pos].t.task.total_tics;
	s.total_trans = task[pos].t.task.total_trans;
	s.remaining_tics = task[pos].t.task.quantum - task[pos].t.task.tics;
	copy_to_user(&s, st, 3*sizeof(int));
	return 0;
}

/* DUP */
int sys_dup(int fd)
{
	int i;
	struct task_struct *cur = current();

	if((fd < 0) || (fd >= CHTABLE_SIZE)) return -EBADF;
	if(!(cur->chtable[fd].used)) return -EBADF;

	for(i = 0; i < CHTABLE_SIZE; ++i)
	{
		if(!(cur->chtable[i].used))
		{
			cur->chtable[i].used = 1;
			cur->chtable[i].opened_file = cur->chtable[fd].opened_file;
			cur->chtable[i].opened_file->n_refs++;
			return i;
		}
	}
	return -EMFILE;
}

/* NI_SYSCALL */
int sys_ni_syscall()
{
	return -ENOSYS;
}
